Suppose a string x declared in main
that is used as an argument for a function helper(x : String)
.
helper(x)
in Java, we give helper
a reference to x.helper(x)
in Rust, we give helper
ownership of x.On Rust’s Memory Management:
- Unlike malloc and free, memory management is about using and not using variables.
- When a function returns, all the data it owns gets dropped.
Let’s look at this Rust code:
fn main() {
let x = format!("Hello World");
;
helper(x);
helper(x)}
fn helper(x : String) {
println!(x);
}
This code cannot compile because when we call helper
the first time, we give ownership of x to it; and so when helper returns, it drops x. As a result, because we try to use x on the second helper(x)
, we get error[E0382]: use of moved value
Let’s look at this Java code:
void main() {
Vector x = ...;
helper(x);
helper(x);
}
void helper(Vector x) {
!(x);
println}
In Java, we give access (references) to things rather than ownership, which entangles their states. When helper
modifies x in Java, it’s immediately visible to main
.
Moreover, we can never know when to free memory; even if main
exits, due to the possibility of helper(x)
making threads.
Non-Copyable: Values move from place to place.
Clone: Run custom code to copy a value.
fn main() {
let x = format!("Hello World");
.clone());
helper(x;
helper(x)}
Copy: Some basic data types are automatically cloned when you use them, as it’s very cheap.
You can never have a reader/writer at the same time.
Both locks last until their reference goes out of scope.
Shared Borrow (&x
): We can borrow variables to create a reference.
More on Borrowed == Immutable:
Mutation is allowed:
- In controlled scenarios with specific APIs (e.g., mutex), or
- When a
mut
value is shared borrowed, it can’t be mutated during the borrow—but regains mutability afterward. (see “Example: Immutability of Shared Borrows”)
As seen in the previous example, this code doesn’t compile.
fn main() {
let x = format!("Hello World");
;
helper(x);
helper(x)}
fn helper(x : String) {
println!(x);
}
In this fix, we borrow the string to create a reference and update helper to take a reference to a string.
fn main() {
let x = format!("Hello World");
let r = &x;
;
helper(r);
helper(r)}
fn helper(x : &String) {
println!(x);
}
r
is a copy type!This is the code from the previous example, with a key change: We made made x mutable, and even push a character to it.
Shouldn’t this not compile because shared referrences are immutable?
fn main() {
let mut x = format!("Hello World");
.push("a");
xlet r = &x;
;
helper(r);
helper(r)}
fn helper(x : &String) {
println!("{}", x);
}
The reason the code compiles is that Rust sees x
as mutable (as we declared it), until it gets borrowed.
r
exists, we’ll get errors.// Does not compile (borrowed == immutable)
fn main() {
let mut x = format!("Hello World");
let r = &x;
;
helper(r);
helper(r).push("a");
x}
// Does compile
fn main() {
let mut x = format!("Hello World");
{
let r = &x;
;
helper(r);
helper(r)}
.push("a");
x}
x
once r
is out of scope.Mutable Borrows (&mut x
):
This Rust code uses borrowing to concatenate two strings:
pub fn main() {
let (mut str1, str2) = two_words();
= join_words(str1, str2);
str1 println!("concatenated string is {:?}", str1);
}
fn two_words() -> (String, String) {
format!("fellow"), format!("Rustaceans"))
(}
fn join_words(mut prefix: String, suffix: String) -> String {
.push(' ');
prefixfor ch in suffix.chars() {
.push(ch);
prefix}
prefix}
This is a modification of the code to use borrowing and modify str1 in place:
pub fn main() {
let (mut str1, str2) = two_words();
&mut str1, &str2);
join_words(println!("concatenated string is {:?}", str1);
}
fn two_words() -> (String, String) {
format!("fellow"), format!("Rustaceans"))
(}
fn join_words(prefix: &mut String, suffix: &String) {
.push(' ');
prefixfor ch in suffix.chars() {
.push(ch);
prefix}
}